//go:build windows
// +build windows

package mem

import (
	"fmt"
	"go-cmd/internal/common"
	"unsafe"

	"golang.org/x/sys/windows"
)

type DWORD uint32
type DWORDLONG uint64

// You can get more information from https://learn.microsoft.com/zh-cn/windows/win32/api/psapi/
// so that you can know why we should import this dll
var (
	modPsApi                 = common.ModPsApi
	modKernel32              = common.ModKernel32
	procGetProcessMemoryInfo = modPsApi.NewProc("GetProcessMemoryInfo")

	// U can see https://learn.microsoft.com/zh-cn/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex
	procGlobalMemoryStatusEx = modKernel32.NewProc("GlobalMemoryStatusEx")
)

const (
	processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION
)

// See https://learn.microsoft.com/zh-cn/windows/win32/api/psapi/ns-psapi-process_memory_counters
// for more information. We need to declare this structure so that we can receive the result of
// system call
type PROCESS_MEMORY_COUNTERS struct {
	CB                         uint32 // the size of structure(byte)
	PageFaultCount             uint32 // the number of fault page
	PeakWorkingSetSize         uint64
	WorkingSetSize             uint64
	QuotaPeakPagedPoolUsage    uint64
	QuotaPagePoolUsage         uint64
	QuotaPeakNonPagedPoolUsage uint64
	QuotaNonPagedPoolUsage     uint64
	PagefileUsage              uint64
	PeakPagefileUsage          uint64
}

// The structure is defined at https://learn.microsoft.com/zh-cn/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
// It is used to receive the result of function GlobalMemoryStatusEx
type MEMORY_STATUS_EX struct {
	DwLength                DWORD
	DwMemoryLoad            DWORD
	UllTotalPhys            DWORDLONG
	UllAvailPhys            DWORDLONG
	UllTotalPageFile        DWORDLONG
	UllAvailPageFile        DWORDLONG
	UllTotalVirtual         DWORDLONG
	UllAvailVirtual         DWORDLONG
	UllAvailExtendedVirtual DWORDLONG
}

/*
Each process will contain stack space, heap space, and data space
So we need to define this info for further development
*/
type ProcessMem struct {
	RSS    uint64 // Residency Set Size
	VMS    uint64 // Total amount of memory committed for running processes
	HWM    uint64 // Peak work size
	Stack  uint64 // the size of stack space
	Locked uint64 // size of physical space locked up by process, which can't be swap to disk
	Swap   uint64 // size of swap space
	Data   uint64 // size of data space
}

// We can use it to get the memory information about one process by its pid.
func GetMemoryInfo(pid int) (*ProcessMem, error) {
	mem := new(PROCESS_MEMORY_COUNTERS)
	handle, err := windows.OpenProcess(processQueryInformation, false, uint32(pid))
	if err != nil {
		return nil, err
	}
	defer windows.CloseHandle(handle)

	r1, _, err := procGetProcessMemoryInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(mem)), uintptr(unsafe.Sizeof(*mem)))
	if r1 == 0 {
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("get process mem state failed, err=%v\n", err)
	}
	result := &ProcessMem{
		RSS: mem.WorkingSetSize,
		VMS: mem.PagefileUsage,
		HWM: mem.PeakWorkingSetSize,
	}
	return result, nil
}

// We can get the Total Memory information about this computer.
// So that we can get the mem info about percent.
func GetVirtualMemStat() (*VirtualMemoryState, error) {
	var winMemInfo MEMORY_STATUS_EX
	winMemInfo.DwLength = DWORD(unsafe.Sizeof(winMemInfo))

	r, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&winMemInfo)))
	if r == 0 && err != nil {
		return nil, err
	}
	ret := &VirtualMemoryState{
		Total:     uint64(winMemInfo.UllTotalPhys),
		Available: uint64(winMemInfo.UllAvailPhys),
		Used:      uint64(winMemInfo.UllTotalPhys - winMemInfo.UllAvailPhys),
	}
	return ret, nil
}
